﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Elderos.Controls;
using TreeBuilder.Settings;

namespace TreeBuilder
{
	/// <summary>
	/// Interaction logic for ArrowLink.xaml
	/// </summary>
	public partial class ArrowLink : UserControl, IBinder, INameScope, IPrintable, IXmlSerializable
	{
		public ArrowLink()
		{
			InitializeComponent();
			Initialize(
				ArrowSettings.Default.Stroke,
				ArrowSettings.Default.Thickness,
				ArrowSettings.Default.DashStyle,
				ArrowSettings.Default.VerticalOffset
				);
		}

		private void Initialize(Color stroke, double thickness, DoubleCollection dashStyle, double verticalOffset)
		{
			MainPath.Stroke = new SolidColorBrush(stroke);
			MainPath.StrokeThickness = thickness;
			MainPath.StrokeDashArray = dashStyle;
			_verticalOffset = verticalOffset;


			_head = new ArrowHead();
			Binding headBinding = new Binding("Visibility");
			headBinding.Source = this;
			_head.SetBinding(VisibilityProperty, headBinding);

			Binding sizeBinding = new Binding("StrokeThickness");
			sizeBinding.Source = MainPath;
			sizeBinding.Converter = new ArrowHeadWidthConverter();
			_head.SetBinding(WidthProperty, sizeBinding);
			_head.SetBinding(HeightProperty, sizeBinding);

			Binding brushBinding = new Binding("Stroke");
			brushBinding.Source = MainPath;
			_head.SetBinding(ArrowHead.BrushProperty, brushBinding);

			_head.MouseRightButtonUp += Head_MouseRightButtonUp;
            _head.MouseLeftButtonUp += MainPath_OnMouseLeftButtonUp;
			AssignEmptyHandlers(_head);
			AssignEmptyHandlers(MainPath);

			NameScope.SetNameScope(Menu, this);
		}

		private void AssignEmptyHandlers(UIElement element)
		{
			element.PreviewMouseDown += HandleEvent;
			element.PreviewMouseLeftButtonDown += HandleEvent;
			element.PreviewMouseRightButtonDown += HandleEvent;
		}

		private void HandleEvent(object sender, MouseEventArgs e)
		{
			e.Handled = true;
		}

		private void Head_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
		{
			this.Menu.IsOpen = true;
			e.Handled = true;
		}

		public INode From { get; set; }

		public INode To { get; set; }

		private double _verticalOffset;
		public double VerticalOffset
		{
			get { return _verticalOffset; }
			set
			{
				_verticalOffset = value;
				RefreshPoints();
			}
		}


		public Brush Brush
		{
			get { return (Brush)GetValue(BrushProperty); }
			set { SetValue(BrushProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Brush.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty BrushProperty =
			DependencyProperty.Register("Brush", typeof(Brush), typeof(ArrowLink), new UIPropertyMetadata(Brushes.Black));

		private ArrowHead _head;

		public ArrowHead Head 
		{
			get { return _head; }
			private set { _head = value; }
		}

		protected override void OnVisualParentChanged(DependencyObject oldParent)
		{
			base.OnVisualParentChanged(oldParent);
			if (_head != null)
			{
				if (oldParent != null)
					((Canvas) oldParent).Children.Remove(_head);

				if (Parent != null && Parent != oldParent)
					((Canvas) Parent).Children.Add(_head);

				RefreshHeadPosition();
			}
		}

		public void RefreshPoints()
		{
			if (From == null || To == null) return;

			Point from = From.BottomCenter;
			Point to = To.BottomCenter;

			if (from.X == to.X)
			{
				Visibility = Visibility.Hidden;
				return;
			}
			Visibility = Visibility.Visible;


			double arcY = Math.Max(from.Y + VerticalOffset, to.Y + VerticalOffset) + _head.Height;

			FromLine.StartPoint = from;
			FromLine.EndPoint = new Point(from.X, arcY);

			ToLine.StartPoint = new Point(to.X, arcY);
			
			RefreshHeadPosition();

			ToLine.EndPoint = new Point(to.X, Canvas.GetTop(_head) + _head.Height);

			if (from.X < to.X)
			{
				ArcFigure.StartPoint = FromLine.EndPoint;
				ArcSegment.Point = ToLine.StartPoint;
			}
			else
			{
				ArcFigure.StartPoint = ToLine.StartPoint;
				ArcSegment.Point = FromLine.EndPoint;
			}

            InvalidateAdorners();
		}

		public event Action<object, DeleteBinderEventArgs> Delete;

		protected virtual void OnDelete(object sender, DeleteBinderEventArgs args)
		{
			Action<object, DeleteBinderEventArgs> handler = Delete;
			if (handler != null) handler(sender, args);
		}

		private void RefreshHeadPosition()
		{
			if (To == null) return;
			Point to = To.BottomCenter;
			double width = _head.Width;
			
			Canvas.SetLeft(_head, to.X - width/2);
			Canvas.SetTop(_head, to.Y);
		}

		#region INameScope Members

		readonly Dictionary<string, object> _items = new Dictionary<string, object>();

		object INameScope.FindName(string name)
		{
			return _items[name];
		}

		void INameScope.RegisterName(string name, object scopedElement)
		{
			_items.Add(name, scopedElement);
		}

		void INameScope.UnregisterName(string name)
		{
			_items.Remove(name);
		}

		#endregion

		private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
		{
			string text = ((TextBox) sender).Text;

			double newValue;
			if (!double.TryParse(text, out newValue))
				return;

			this.MainPath.StrokeThickness = newValue;
			RefreshPoints();
		}

		private void DeleteMenuItem_Click(object sender, RoutedEventArgs e)
		{
			OnDelete(sender,
			         new DeleteBinderEventArgs(this,
			                                   canvas => canvas.Children.Remove(_head),
			                                   canvas => canvas.Children.Add(_head)));
		}

		#region Implementation of IPrintable

		public bool IsBeingPrinted
		{
			get { return (bool)GetValue(IsBeingPrintedProperty); }
			set { SetValue(IsBeingPrintedProperty, value); }
		}

		// Using a DependencyProperty as the backing store for IsBeingPrinted.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty IsBeingPrintedProperty =
			DependencyProperty.Register("IsBeingPrinted", typeof(bool), typeof(ArrowLink), new UIPropertyMetadata(false));

		#endregion

		#region Implementation of IXmlSerializable

		/// <summary>
		/// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
		/// </returns>
		public XmlSchema GetSchema()
		{
			return null;
		}

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. </param>
		public void ReadXml(XmlReader reader)
		{
			XDocument xdoc = XDocument.Load(reader);
			var root = xdoc.Root;

			Color color = (Color)XamlReader.Parse(root.Element("Color").Value);
			double thickness = double.Parse(root.Element("Thickness").Value.Replace(",", "."), CultureInfo.InvariantCulture);
			DoubleCollection dashStyle = DoubleCollection.Parse(root.Element("DashStyle").Value.Replace(",", "."));
			double verticalOffset = double.Parse(root.Element("VerticalOffset").Value.Replace(",", "."), CultureInfo.InvariantCulture);

			Initialize(color, thickness, dashStyle, verticalOffset);
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. </param>
		public void WriteXml(XmlWriter writer)
		{
			writer.WriteElementString("Color", XamlWriter.Save(((SolidColorBrush)MainPath.Stroke).Color));
			writer.WriteElementString("Thickness", MainPath.StrokeThickness.ToString());
			writer.WriteElementString("DashStyle", MainPath.StrokeDashArray == null ? "" : MainPath.StrokeDashArray.ToString());
			writer.WriteElementString("VerticalOffset", VerticalOffset.ToString());
			writer.WriteElementString("From", From.Guid.ToString());
			writer.WriteElementString("To", To.Guid.ToString());
		}

		#endregion

		private bool ValidateDouble(string arg)
		{
			return ValidatableTextBox.ValidateDouble(arg);
		}

		private bool ValidateDoubleCollection(string arg)
		{
			return ValidatableTextBox.ValidateDoubleCollection(arg);
		}

		private bool ValidateThickness(string arg)
		{
			if (!ValidatableTextBox.ValidateDouble(arg)) return false;
			double value;
			if (!double.TryParse(arg.Replace(",", "."), NumberStyles.Number, CultureInfo.InvariantCulture, out value))
				return false;

			return value <= ApplicationSettings.Default.MaxBinderThickness
				   && value >= ApplicationSettings.Default.MinBinderThickness;
		}

		private bool ParseDoubleCollection(string arg)
		{
			return ValidatableTextBox.ParseDoubleCollection(arg);
		}

		private bool ParseDouble(string arg)
		{
			return ValidatableTextBox.ParseDouble(arg);
		}

		private void Menu_Closed(object sender, RoutedEventArgs e)
		{
			VerticalOffset = double.Parse(VerticalOffsetBox.Value.Replace(",", "."), CultureInfo.InvariantCulture);
			OnMenuClosed(sender, new MenuChangesEventArgs(this));
		}

		private void Menu_Opened(object sender, RoutedEventArgs e)
		{
			VerticalOffsetBox.Text = VerticalOffset.ToString();
			OnMenuOpened(sender, new MenuChangesEventArgs(this));
		}

		public Dictionary<string, object> MakeSnapshot()
		{
			var snapshot = new Dictionary<string, object>();
			snapshot.Add("Thickness", MainPath.StrokeThickness);
			snapshot.Add("Dashes", MainPath.StrokeDashArray);
			snapshot.Add("VerticalOffset", VerticalOffset);
			snapshot.Add("Brush", MainPath.Stroke);

			return snapshot;
		}

		public void RestoreSnapshop(Dictionary<string, object> snapshot)
		{
			if (snapshot.ContainsKey("Thickness") && MainPath.StrokeThickness != (double) snapshot["Thickness"])
				MainPath.StrokeThickness = (double)snapshot["Thickness"];
			if (snapshot.ContainsKey("Dashes") && MainPath.StrokeDashArray != (DoubleCollection)snapshot["Dashes"])
				MainPath.StrokeDashArray = (DoubleCollection)snapshot["Dashes"];
			if (snapshot.ContainsKey("VerticalOffset") && VerticalOffset != (double)snapshot["VerticalOffset"])
				VerticalOffset = (double)snapshot["VerticalOffset"];
			if (snapshot.ContainsKey("Brush") && MainPath.Stroke != (Brush)snapshot["Brush"])
				MainPath.Stroke = (Brush)snapshot["Brush"];
		}

		public bool DetectChanges(Dictionary<string, object> snapshot)
		{
			if (snapshot.ContainsKey("Thickness") && MainPath.StrokeThickness != (double) snapshot["Thickness"])
				return true;
			if (snapshot.ContainsKey("Dashes") && MainPath.StrokeDashArray != (DoubleCollection)snapshot["Dashes"])
				return true;
			if (snapshot.ContainsKey("VerticalOffset") && VerticalOffset != (double)snapshot["VerticalOffset"])
				return true;
			if (snapshot.ContainsKey("Brush") && MainPath.Stroke != (Brush)snapshot["Brush"])
				return true;
			return false;
		}

		public event Action<object, MenuChangesEventArgs> MenuOpened;

		protected virtual void OnMenuOpened(object sender, MenuChangesEventArgs e)
		{
			Action<object, MenuChangesEventArgs> handler = MenuOpened;
			if (handler != null) handler(sender, e);
		}

		public event Action<object, MenuChangesEventArgs> MenuClosed;

		protected virtual void OnMenuClosed(object sender, MenuChangesEventArgs e)
		{
			Action<object, MenuChangesEventArgs> handler = MenuClosed;
			if (handler != null) handler(sender, e);
		}

	    private void ColorPicker_OnSelectedColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
	    {
	        MainPath.Stroke = new SolidColorBrush(e.NewValue);
	    }

	    public Point ArcBottomPoint
	    {
	        get
	        {
	            var point = new Point(FromLine.EndPoint.X, this.ActualHeight);
                point.Offset((ToLine.StartPoint.X - point.X)/2, 0);
	            return point;
	        }
	    }

        public bool Intersects(Rect selectionRect)
        {
            var geometry = new RectangleGeometry(selectionRect);

            var intersection = geometry.FillContainsWithDetail(MainPath.Data);

            return intersection != IntersectionDetail.Empty;
        }

        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set
            {
                SetValue(IsSelectedProperty, value);
                InvalidateAdorners();
            }
        }

        private void InvalidateAdorners()
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(this);
            if (adornerLayer == null) return;

            var adorners = adornerLayer.GetAdorners(this);
            if (adorners == null) return;

            foreach (var adorner in adorners)
            {
                adorner.InvalidateVisual();
            }
        }

        public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.Register("IsSelected", typeof(bool), typeof(ArrowLink), new PropertyMetadata(false));

        public event Action<object, SelectionEventArgs> Selected;

	    protected virtual void OnSelected(object arg1, SelectionEventArgs arg2)
	    {
	        Action<object, SelectionEventArgs> handler = Selected;
	        if (handler != null) handler(arg1, arg2);
	    }

	    public void InitializeSelectionAdorners()
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(this);
            var adorner = new ArrowAdorner(this);

            var binding = new Binding("IsSelected");
            binding.Source = this;

            adorner.SetBinding(EllipseAdorner.IsActiveProperty, binding);
            adornerLayer.Add(adorner);
        }

	    private void MainPath_OnLoaded(object sender, RoutedEventArgs e)
	    {
            InitializeSelectionAdorners();
	    }

	    private void MainPath_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
	    {
	        OnSelected(this, new SelectionEventArgs(this));
	    }
	}
}
